﻿using System;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Xml.Linq;

using FirstFloor.Documents.Annotations.Storage;

namespace FirstFloor.Documents.Presentation.Annotations
{
    /// <summary>
    /// Reads and writes Ink annotations.
    /// </summary>
    /// <remarks>
    /// The Ink factory provides persistence to an XML based store.
    /// </remarks>
    public class InkFactory
        : AnnotationFactory<Ink>
    {
        private const string NamespaceXaml = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
        /// <summary>
        /// Initializes a new instance of the <see cref="InkFactory"/> class.
        /// </summary>
        public InkFactory()
            : base(Ink.TypeInk)
        {
        }

        /// <summary>
        /// Creates the annotation.
        /// </summary>
        /// <returns>The annotation.</returns>
        public override Ink CreateAnnotation()
        {
            return new Ink();
        }

        /// <summary>
        /// Read an annotation from given element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns>The annotation.</returns>
        protected override Ink Read(XElement element)
        {
            var ink = base.Read(element);
            ink.PageNumber = (int)element.Attribute("PageNumber");

            // support both old (verbose XAML) and new style
            var strokesElement = element.Element(XName.Get("StrokeCollection", NamespaceXaml));
            if (strokesElement != null) {
                // load using XAMLreader
                var strokes = (StrokeCollection)XamlReader.Load(strokesElement.ToString(SaveOptions.None));
                foreach (var stroke in strokes) {
                    ink.Strokes.Add(stroke);
                }
            }
            else {
                // load new more concise format as defined in Write method
                strokesElement = element.Element(XName.Get("Strokes", AnnotationStore.NamespaceAnnotations));
                if (strokesElement != null) {
                    var strokes = from stroke in strokesElement.Elements(XName.Get("Stroke", AnnotationStore.NamespaceAnnotations))
                                  select new Stroke {
                                      DrawingAttributes = new DrawingAttributes {
                                          Width = (double)stroke.Attribute("Width"),
                                          Height = (double)stroke.Attribute("Height"),
                                          Color = ParseArgb((string)stroke.Attribute("Color"))
                                      },
                                      StylusPoints = LoadStylusPoints((string)stroke.Attribute("Points"))
                                  };

                    foreach (var stroke in strokes) {
                        ink.Strokes.Add(stroke);
                    }
                }
            }

            return ink;
        }

        /// <summary>
        /// Write an annotation.
        /// </summary>
        /// <param name="annotation">The annotation.</param>
        /// <returns>
        /// The XML element representing the annotation.
        /// </returns>
        protected override XElement Write(Ink annotation)
        {
            var element = base.Write(annotation);
            element.Add(
                new XAttribute("PageNumber", annotation.PageNumber),
                new XElement(XName.Get("Strokes", AnnotationStore.NamespaceAnnotations),
                    from stroke in annotation.Strokes
                    select new XElement(XName.Get("Stroke", AnnotationStore.NamespaceAnnotations),
                        new XAttribute("Width", stroke.DrawingAttributes.Width),
                        new XAttribute("Height", stroke.DrawingAttributes.Height),
                        new XAttribute("Color", stroke.DrawingAttributes.Color),
                        new XAttribute("Points", SaveStylusPoints(stroke.StylusPoints)))));

            return element;
        }

        private static Color ParseArgb(string value)
        {
            Color c;
            if (!ParserHelper.TryParseColor(value, out c)) {
                c = Colors.Black;
            }
            return c;
        }

        private static StylusPointCollection LoadStylusPoints(string value)
        {
            var result = new StylusPointCollection();
            var lastPos = 0;
            while (true) {
                var i = value.IndexOf(' ', lastPos);
                if (i != -1) {
                    var parts = value.Substring(lastPos, i - lastPos).Split(',');
                    if (parts.Length == 2) {
                        result.Add(new StylusPoint {
                            X = double.Parse(parts[0], CultureInfo.InvariantCulture),
                            Y = double.Parse(parts[1], CultureInfo.InvariantCulture)
                        });
                    }

                    lastPos = i + 1;
                }
                else {
                    break;
                }
            }

            return result;
        }

        private static string SaveStylusPoints(StylusPointCollection points)
        {
            var result = new StringBuilder();
            foreach (var p in points) {
                if (result.Length > 0) {
                    result.Append(' ');
                }

                // write max 1 decimal to preserve space
                result.AppendFormat(CultureInfo.InvariantCulture, "{0:0.0},", p.X);
                result.AppendFormat(CultureInfo.InvariantCulture, "{0:0.0}", p.Y);
            }

            return result.ToString();
        }
    }
}
